/**
 * Project Name:MeatballMatcher.java
 * File Name:MeatballMatcher.java
 * Date:2019/4/8 9:27
 * Copyright (c) 2019, zhang.xiangyu@foxmail.com All Rights Reserved.
 */
package com.meatball.base.repository.matcher;


import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import java.util.*;
import java.util.function.Function;

/**
 * @Title: MeatballMatcher.java
 * @Description: todo(sql匹配)
 * @Author: 張翔宇
 * @Date: 2019/4/8 9:27
 * @Version: V1.0
 */
public interface MeatballMatcher {
    static MeatballMatcher matching () {
        return matchingAll();
    }

    static MeatballMatcher matchingAll() {
        return new TypedMeatballMatcher().withMode(MatchMode.ALL);
    }

    interface MatcherConfigurer<T> {
        void configureMatcher(T matcher);
    }

    default MeatballMatcher withMatcher(String propertyPath, MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) {
        Assert.hasText(propertyPath, "PropertyPath must not be empty(propertyath不能为空)!");
        Assert.notNull(matcherConfigurer, "MatcherConfigurer must not be empty(MatcherConfigurer不能是空的)!");

        GenericPropertyMatcher genericPropertyMatcher = new GenericPropertyMatcher();
        matcherConfigurer.configureMatcher(genericPropertyMatcher);

        return withMatcher(propertyPath, genericPropertyMatcher);
    }

    MeatballMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher);

    MatchMode getMatchMode();

    PropertySpecifiers getPropertySpecifiers();

    /**
     * Define specific property handling for a Dot-Path.
     *
     * @author Christoph Strobl
     * @author Mark Paluch
     * @since 1.12
     */
    @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
    @EqualsAndHashCode
    class PropertySpecifier {
        String path;
        @Nullable StringMatcher stringMatcher;
        @Nullable Boolean ignoreCase;
        PropertyValueTransformer valueTransformer;
        @Nullable Date start;
        @Nullable Date end;
        @Nullable Object parameter;

        /**
         * Creates new {@link PropertySpecifier} for given path.
         *
         * @param path Dot-Path to the property. Must not be {@literal null}.
         */
        PropertySpecifier(String path) {

            Assert.hasText(path, "Path must not be null/empty!");
            this.path = path;
            this.stringMatcher = null;
            this.ignoreCase = null;
            this.valueTransformer = NoOpPropertyValueTransformer.INSTANCE;
            this.start = null;
            this.end = null;
            this.parameter = null;
        }

//        PropertySpecifier(String path, Date start, Date end) {
//            Assert.hasText(path, "Path must not be null/empty!");
//            this.path = path;
//            this.stringMatcher = null;
//            this.ignoreCase = null;
//            this.start = start;
//            this.end = end;
//            this.valueTransformer = NoOpPropertyValueTransformer.INSTANCE;
//        }

        /**
         * Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
         * {@link StringMatcher} in the returned instance.
         *
         * @param stringMatcher must not be {@literal null}.
         * @return
         */
        public PropertySpecifier withStringMatcher(StringMatcher stringMatcher) {

            Assert.notNull(stringMatcher, "StringMatcher must not be null!");
            return new PropertySpecifier(this.path, stringMatcher, this.ignoreCase, this.valueTransformer, this.start, this.end, this.parameter);
        }

        /**
         * Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
         * {@code ignoreCase}.
         *
         * @param ignoreCase must not be {@literal null}.
         * @return
         */
        public PropertySpecifier withIgnoreCase(boolean ignoreCase) {
            return new PropertySpecifier(this.path, this.stringMatcher, ignoreCase, this.valueTransformer, this.start, this.end, this.parameter);
        }

        /**
         * Creates a new {@link PropertySpecifier} containing all values from the current instance and sets
         * {@link PropertyValueTransformer} in the returned instance.
         *
         * @param valueTransformer must not be {@literal null}.
         * @return
         */
        public PropertySpecifier withValueTransformer(PropertyValueTransformer valueTransformer) {

            Assert.notNull(valueTransformer, "PropertyValueTransformer must not be null!");
            return new PropertySpecifier(this.path, this.stringMatcher, this.ignoreCase, valueTransformer, this.start, this.end, this.parameter);
        }

        /**
         * @title: withValueBettown
         * @description: todo(区间查询)
         * @params: [valueTransformer]
         * @return: com.meatball.base.repository.matcher.MeatballMatcher.PropertySpecifier    返回类型
         * @author: 張翔宇
         * @date: 2019/4/13 14:23
         */
        public PropertySpecifier withValueBettown(StringMatcher stringMatcher, Date start, Date end) {

            Assert.notNull(valueTransformer, "PropertyValueTransformer must not be null!");
            return new PropertySpecifier(this.path, stringMatcher, this.ignoreCase, this.valueTransformer, start, end, this.parameter);
        }

        /**
         * @title: withValueIn
         * @description: todo(in查询)
         * @params: [stringMatcher, parameter]
         * @return: com.meatball.base.repository.matcher.MeatballMatcher.PropertySpecifier    返回类型
         * @author: 張翔宇
         * @date: 2019/4/17 17:59
         */
        public PropertySpecifier withValueIn (StringMatcher stringMatcher, Object parameter) {
            Assert.notNull(valueTransformer, "PropertyValueTransformer must not be null!");
            return new PropertySpecifier(this.path, stringMatcher, this.ignoreCase, this.valueTransformer, this.start, this.end, parameter);
        }

        /**
         * @title: getPath
         * @description: todo(获取属性路径)
         * @params: []
         * @return: java.lang.String    返回类型
         * @author: 張翔宇
         * @date: 2019/4/9 16:55
         */
        public String getPath() {
            return path;
        }

        /**
         * Get the {@link StringMatcher}.
         *
         * @return can be {@literal null}.
         */
        @Nullable
        public StringMatcher getStringMatcher() {
            return stringMatcher;
        }

        public Date getStart() {
            return  start;
        }

        public Date getEnd() {
            return end;
        }

        public Object getParameter() {
            return this.parameter;
        }

        /**
         * @return {@literal null} if not set.
         */
        @Nullable
        public Boolean getIgnoreCase() {
            return ignoreCase;
        }

        /**
         * Get the property transformer to be applied.
         *
         * @return never {@literal null}.
         */
        public PropertyValueTransformer getPropertyValueTransformer() {
            return valueTransformer == null ? NoOpPropertyValueTransformer.INSTANCE : valueTransformer;
        }

        /**
         * Transforms a given source using the {@link PropertyValueTransformer}.
         *
         * @param source
         * @return
         * @deprecated since 2.0, use {@link #transformValue(Optional)} instead.
         */
        @Deprecated
        public Object transformValue(Object source) {
            return transformValue(Optional.ofNullable(source)).orElse(null);
        }

        /**
         * Transforms a given source using the {@link PropertyValueTransformer}.
         *
         * @param source
         * @return
         */
        public Optional<Object> transformValue(Optional<Object> source) {
            return getPropertyValueTransformer().apply(source);
        }
    }

    @EqualsAndHashCode
    class GenericPropertyMatcher {
        @Nullable
        StringMatcher stringMatcher = null;
        @Nullable Boolean ignoreCase = null;
        @Nullable Date start = null;
        @Nullable Date end = null;
        @Nullable Object parameter = null;
        PropertyValueTransformer valueTransformer = NoOpPropertyValueTransformer.INSTANCE;

        public GenericPropertyMatcher() {}

        /**
         * @title: contains
         * @description: todo(包含模式)
         * @params: []
         * @return: com.meatball.base.repository.matcher.MeatballMatcher.GenericPropertyMatcher    返回类型
         * @author: 張翔宇
         * @date: 2019/4/9 17:00
         */
        public GenericPropertyMatcher contains() {
            this.stringMatcher = StringMatcher.CONTAINING;
            return this;
        }

        /**
         * @title: between
         * @description: todo(区间模式)
         * @params: []
         * @return: com.meatball.base.repository.matcher.MeatballMatcher.GenericPropertyMatcher    返回类型
         * @author: 張翔宇
         * @date: 2019/4/9 17:00
         */
        public GenericPropertyMatcher between(Date start, Date end) {
            this.start = start;
            this.end = end;
            this.stringMatcher = StringMatcher.BETWEEN;
            return this;
        }

        /**
         * @title: in
         * @description: todo(in 查询)
         * @params: [parameter]
         * @return: com.meatball.base.repository.matcher.MeatballMatcher.GenericPropertyMatcher    返回类型
         * @author: 張翔宇
         * @date: 2019/4/17 17:53
         * @param parameter
         */
        public GenericPropertyMatcher in(Object parameter) {
            this.parameter = parameter;
            this.stringMatcher = StringMatcher.IN;
            return this;
        }
    }

    @EqualsAndHashCode
    class PropertySpecifiers {
        private final Map<String, PropertySpecifier> propertySpecifiers = new LinkedHashMap<>();
        PropertySpecifiers() {}

        PropertySpecifiers(PropertySpecifiers propertySpecifiers) {
            this.propertySpecifiers.putAll(propertySpecifiers.propertySpecifiers);
        }

        public void add(PropertySpecifier specifier) {
            Assert.notNull(specifier, "PropertySpecifier must not be null!");
            propertySpecifiers.put(specifier.getPath(), specifier);
        }

        public boolean hasSpecifierForPath(String path) {
            return propertySpecifiers.containsKey(path);
        }

        public PropertySpecifier getForPath(String path) {
            return propertySpecifiers.get(path);
        }

        public boolean hasValues() {
            return !propertySpecifiers.isEmpty();
        }

        public Collection<PropertySpecifier> getSpecifiers() {
            return propertySpecifiers.values();
        }
    }

    interface PropertyValueTransformer extends Function<Optional<Object>, Optional<Object>> {

        /**
         * @title: convert
         * @description: todo(用于调用Spring的转换器接口的客户机的向后兼容性)
         * @params: [source]
         * @return: java.lang.Object    返回类型
         * @author: 張翔宇
         * @date: 2019/4/8 18:16
         */
        @Deprecated
        default Object convert(Object source) {
            return apply(Optional.ofNullable(source)).orElse(null);
        }
    }

    enum NoOpPropertyValueTransformer implements PropertyValueTransformer {

        INSTANCE;

        /*
         * (non-Javadoc)
         * @see java.util.function.Function#apply(java.lang.Object)
         */
        @Override
        @SuppressWarnings("null")
        public Optional<Object> apply(Optional<Object> source) {
            return source;
        }
    }

    enum NullHandler {
        INCLUDE, IGNORE
    }

    enum StringMatcher {

        /**
         * 默认
         */
        DEFAULT,
        /**
         * 完全匹配
         */
        EXACT,
        /**
         * 匹配开始字符
         */
        STARTING,
        /**
         * 匹配结尾字符
         */
        ENDING,
        /**
         * 包含
         */
        CONTAINING,
        /**
         * 区间匹配
         */
        BETWEEN,
        /**
         *
         */
        IN,
        /**
         * 使用正则表达式
         */
        REGEX;
    }

    enum MatchMode {
        ALL, ANY;
    }
}
